/*
 * preprocessor.c
 *
 *  Created on: 07/11/2010
 *      Author: dela
 */

#include "preprocessor.h"
#include "listaAux.h"
#include "../domain/ListaSimple.h"
#include "../fileSystem/fileStructs.h"
#include "../fileSystem/matrix_mem.h"
#include "../fileSystem/file.h"
#include <stdbool.h>
#include <time.h>
#include <malloc.h>
#include <string.h>

bool allReadyDone( mHandler *fd, int personId );
int processRow( mHandler *fd, TGraph *graph, int personId, int maxPersonId );
void giveIds( TNode *node2, TNode *node, int maxPersonId );
int process( mHandler *fd, TNode *node2, TNode *node );
void refreshNodesToProcess( TListaSimple *nodesToProcess, TListaSimple *nodesToProcessAux );
void getNodes( TNode *node, TListaSimple *nodes, int maxPersonId );
int completeRow( mHandler *fd, bool *allreadyProcess, int maxPersonId );
void initializeChecks (bool* checked, int n);
void initializeDistances(int **distance, int n);
int processDistances (mHandler* fd, int **distance, int nodeId);
bool createFiles(fHandler** fp, fHandler** ff, int maxPersonId, int maxFilmId, char* filename_prefix);

/**
 * Genera los archivos de peliculas y personas con sus nombres
 */
void preprocessor_generateFiles( TListaSimple* films, TListaSimple* people, char *filename_prefix) {
	fHandler* fp = 0;
	fHandler* ff = 0;
	if (!createFiles(&fp, &ff, ls_Tamanio(*people), ls_Tamanio(*films), filename_prefix)) {
		return;
	}

	if (ls_MoverCorriente(people, LS_PRIMERO)) {
		do {
			regPer *personReg = 0;
			ls_ElemCorriente(*people, (void*)&personReg);
			R_WRITE(fp, lsAux_getIdexOfPerson(people, personReg->nombre), personReg);
		} while (ls_MoverCorriente(people, LS_SIGUIENTE));
	}

	if (ls_MoverCorriente(films, LS_PRIMERO)) {
		do {
			char* film;
			regPel filmReg;
			ls_ElemCorriente(*films, (void*)&film);
			int size = strlen(film) + 1;
			if (sizeof(filmReg.nombre) >= size) {
				strncpy(filmReg.nombre, film, size);
			} else {
				strncpy(filmReg.nombre, film, sizeof(filmReg.nombre));
				filmReg.nombre[sizeof(filmReg.nombre)-1] = '\0';
			}
			strncpy(filmReg.nombre, film, 4);
			R_WRITE(ff, lsAux_getIdexOfString(films, film), &filmReg);
		} while (ls_MoverCorriente(films, LS_SIGUIENTE));
	}

	R_CLOSE(ff);
	R_CLOSE(fp);
}

/**
 *
 */
void preprocessor_generateMatrix( TGraph *graph, char *fileName, int maxPersonId ) {
	time_t seconds; //TODO BORRAR
	time_t inicio; //TODO BORRAR
	inicio = time (NULL); //TODO BORRAR
	int i = 0; //TODO BORRAR
	int parts = 200;
	int p = 0;

	//Creo archivo y lo abro:
	M_CREATE( fileName, sizeof(regMat), maxPersonId+1 );
	mHandler *fd = M_OPEN( fileName, READ_WRITE );

	//preproceso para todas las personas:
	int personId;
	if (maxPersonId/parts > 0) {
		for (personId = maxPersonId/parts; personId >= 0; personId--) {
			for (p = 0; p < parts; p++) {
				seconds = time (NULL); seconds = seconds - inicio; //TODO BORRAR
				printf("Preprocesando id=%i... [Time=%f] ", personId + (p*(maxPersonId/parts)), seconds/60.0); //TODO Borrar.
				if ( !allReadyDone( fd, personId + (p*(maxPersonId/parts)) ) ) {
					i = i + processRow( fd, graph, personId + (p*(maxPersonId/parts)), maxPersonId );
					seconds = time (NULL); seconds = seconds - inicio; //TODO BORRAR
					printf("OK!  [Time=%f]\n", seconds/60.0); //TODO Borrar.
				} else {
					printf("OK! (ya estaba completa)\n"); //TODO Borrar.
				}
				printf("Total de celdas ya grabadas: %i\n", i); //TODO Borrar.
				printf("Van escritas el %.2f%% de celdas\n", ((float)i / (((float)((maxPersonId * maxPersonId) - maxPersonId ))/2.0) ) * 100.0);
			}
		}
	}

	// Proceso los que restan procesar del resto de la división maxPersonId/parts
	for (personId = maxPersonId; personId >= maxPersonId - (maxPersonId % parts); personId--) {
		seconds = time (NULL); seconds = seconds - inicio; //TODO BORRAR
		printf("Preprocesando id=%i... [Time=%f] ", personId, seconds/60.0); //TODO Borrar.
		if ( !allReadyDone( fd, personId ) ) {
			i = i + processRow( fd, graph, personId, maxPersonId );
			seconds = time (NULL); seconds = seconds - inicio; //TODO BORRAR
			printf("OK!  [Time=%f]\n", seconds/60.0); //TODO Borrar.
		} else {
			printf("OK! (ya estaba completa)\n"); //TODO Borrar.
		}
		printf("Total de celdas ya grabadas: %i\n", i); //TODO Borrar.
		printf("Van escritas el %.2f%% de celdas\n", ((float)i / (((float)((maxPersonId * maxPersonId) - maxPersonId ))/2.0) ) * 100.0);
	}

	printf("***PROCESO COMPLETADO***\n");


	M_CLOSE( fd );
}

/**
 * TODO borrar
 */
void printNodes( TListaSimple *nodes ) {
	printf("   nodes:");
	TNode *node2;
	if ( ls_MoverCorriente( nodes, LS_PRIMERO ) ) {
		do { //Proceso todos los nodos adyacentes al nodo que está siendo analizado.
			ls_ElemCorriente( *nodes, (void**)&node2 );
			printf( "%i,", node2->id );
		} while ( ls_MoverCorriente( nodes, LS_SIGUIENTE ) );
	}
	printf("\n");
}

/**
 * TODO borrar
 */
void printIds( TNode *node ) {
	printf("   ids:");
	int *id;
	if ( ls_MoverCorriente( &(node->ids), LS_PRIMERO ) ) {
		do {
			ls_ElemCorriente( node->ids, (void**)&id );
			printf( "%i,", *id );
		} while ( ls_MoverCorriente( &(node->ids), LS_SIGUIENTE ) );
	}
	printf("\n");
}


void initializeChecks (bool* checked, int n) {
	int i = 0;
	for (i = 0; i < n + 1; i++) {
		checked[i] = false;
	}
}

void initializeDistances(int **distance, int n) {
	int i = 0;
	for (i = 0; i < n + 1; i++) {
		distance[i] = (int*) malloc (sizeof(int) * 2);
		distance[i][0] = -3;
		distance[i][1] = -3;
	}
}


int processDistances (mHandler* fd, int **distance, int nodeId) {
	int* actualNode;
	actualNode = distance[nodeId];
	int m = 0;
	int dist = 1;
	int relatedId = actualNode[0];
	int filmId = actualNode[1];
	int res = RES_OK;
	while (actualNode[0] != -1) {
		if (!M_EXISTE_REG(fd, nodeId, actualNode[0])) {
			regMat reg;
			if (dist == 1) {
				reg.idPer = -1;
			} else {
				reg.idPer = relatedId;
			}
			reg.idPel = filmId;
			reg.dist = dist;
			res = M_WRITE(fd, nodeId, actualNode[0], &reg);
			if (res != RES_OK) {
				printf("MATRIZ ERROR ESCRIBIENDO, FIL: %d COL: %d", nodeId, actualNode[0]);
			} else {
				m++;
			}
		}
//		relatedId = actualNode[0];
		actualNode = distance[actualNode[0]];
		dist++;
	}


	return m;
}

int processRow( mHandler *fd, TGraph *graph, int personId, int maxPersonId ) {
	int **distance;
	distance = (int**) malloc (sizeof(int) * (maxPersonId + 1));
	bool breakFlag = false;
	initializeDistances(distance, maxPersonId);
	bool* checked = (bool*) malloc (sizeof(bool) * (maxPersonId + 1));
	initializeChecks(checked, maxPersonId);
	bool* addedToNextIteration = (bool*) malloc (sizeof(bool) * (maxPersonId + 1));
	initializeChecks(addedToNextIteration, maxPersonId);
	int reachedLevel = 1;
	int m=0, i=0;//TODO BORRAR
	int t=0;

	// proceso los nodos cercanos
	TNode* node;
	TNode* auxNode, *auxNode2;
	TListaSimple currentNodeNodes, nodesForNextIteration, nodesForNextIteration2;
	ls_Crear(&currentNodeNodes);
	ls_Crear(&nodesForNextIteration);
	ls_Crear(&nodesForNextIteration2);
	graph_getPersonNode( graph, &node, personId );
	getNodes( node, &currentNodeNodes, maxPersonId );
	// Cargo la distancia al primer nodo y realizo la primer pasada.
	distance[node->id][0] = -1;
	if (ls_MoverCorriente(&currentNodeNodes, LS_PRIMERO)) {
		do {
			ls_ElemCorriente(currentNodeNodes, (void**)&auxNode);

			if (distance[auxNode->id][0] == -3) {
				distance[auxNode->id][0] = node->id;
				distance[auxNode->id][1] = auxNode->idFilm;
			}

			m+= processDistances(fd, distance, auxNode->id);

			if (!addedToNextIteration[auxNode->id]) {
				ls_Insertar(&nodesForNextIteration, LS_PRIMERO, auxNode);
				addedToNextIteration[auxNode->id] = true;
			}

		} while (ls_MoverCorriente(&currentNodeNodes, LS_SIGUIENTE) != false);
	}
	checked[node->id] = true;

	// proceso los siguientes niveles hasta que se terminen los nodos a procesar
	if (ls_MoverCorriente(&nodesForNextIteration, LS_PRIMERO)) {
		while (!ls_Vacia(nodesForNextIteration) /*&& reachedLevel < 7*/) {

				ls_ElemCorriente(nodesForNextIteration, (void**)&auxNode);
				ls_BorrarCorrienteLight(&nodesForNextIteration);
				getNodes( auxNode, &currentNodeNodes, maxPersonId );

				if (ls_MoverCorriente((&currentNodeNodes), LS_PRIMERO)) {
					do {
						ls_ElemCorriente(currentNodeNodes, (void**)&auxNode2);
						if (!checked[auxNode2->id]) {
							if (distance[auxNode2->id][0] == -3) {
								distance[auxNode2->id][0] = auxNode->id;
								distance[auxNode2->id][1] = auxNode2->idFilm;
								m+=processDistances(fd, distance, auxNode2->id);

								if (!addedToNextIteration[auxNode2->id]) {
									ls_Insertar(&nodesForNextIteration2, LS_PRIMERO, auxNode2);
									addedToNextIteration[auxNode2->id] = true;
								}
							}
						}
					} while (ls_MoverCorriente(&currentNodeNodes, LS_SIGUIENTE) != false);
				}

				checked[auxNode->id] = true;

			// Si ya procese todo el nivel cargo en la lista de nodos a procesar todos los que encontre del nivel siguiente.
			if (ls_Vacia(nodesForNextIteration)) {
				ls_Vaciar(&nodesForNextIteration);
				nodesForNextIteration = nodesForNextIteration2;
				ls_Crear(&nodesForNextIteration2);
				reachedLevel++;
				i++;
				if ( allReadyDone( fd, personId ) ) {
					breakFlag = true;
					break;
				}
			}
		}
	}
	ls_VaciarLight( &nodesForNextIteration );
	ls_VaciarLight( &nodesForNextIteration2 );
	ls_VaciarLight( &currentNodeNodes );
	if ( !breakFlag ) {
		m = m + completeRow( fd, checked, maxPersonId );
	}

	for (t=0; t < maxPersonId + 1; t++) {
		free(distance[t]);
	}
	free(distance);
	free(checked);
	free(addedToNextIteration);
	return m;
}

///**
// *
// */
//int processRow( fHandler *fd, TGraph *graph, int personId, int maxPersonId ) {
//	TNode *node, *node2;int processDistances (fHandler* fd, int **distance, int nodeId)
//	bool allreadyProcess[maxPersonId+1];
//	bool allreadyAddToList[maxPersonId+1]; //nodos que ya fueron agregados a nodesToProcess
//	TListaSimple nodesToProcess, nodesToProcessAux, nodes;
//	ls_Crear( &nodesToProcess );
//	ls_Crear( &nodesToProcessAux );
//	ls_Crear( &nodes );
//	int i;
//	for ( i = 0; i <= maxPersonId; i++ )
//		allreadyProcess[i] = false;
//	for ( i = 0; i <= maxPersonId; i++ )
//		allreadyAddToList[i] = false;
//	i = 0; //TODO BORRAR
//	int j,k,m; //TODO BORRAR
//	m=0;//TODO BORRAR
//	//graph_reset( graph );
//	graph_getPersonNode( graph, &node, personId );
//	ls_Insertar( &nodesToProcess, LS_PRIMERO, node );
//	while ( !ls_Vacia( nodesToProcess ) ) { //Mientras halla nodos a procesar:
//		ls_MoverCorriente( &(nodesToProcess), LS_PRIMERO );
//		do { //Analizo un Nodo.
//			ls_ElemCorriente( nodesToProcess, (void**)&node );
//			getNodes( node, &nodes, maxPersonId ); //Obtengo los nodos a distancia 1
//			//printf("  Procesando nodo id=%i\n", node->id); //TODO borrar...
//			//printIds( node ); //TODO borrar...
//			//printNodes( &nodes ); //TODO borrar...
//			if ( ls_MoverCorriente( &nodes, LS_PRIMERO ) ) {
//				do { //Proceso todos los nodos adyacentes al nodo que está siendo analizado.
//					ls_ElemCorriente( nodes, (void**)&node2 );
//					if ( !allreadyProcess[node2->id] ) { //si ya no está analizado.
//						//Le paso la lista de ids:
//						giveIds( node2, node, maxPersonId );
//						//Proceso la lista de ids:
//						m = m + process( fd, node2, node );
//						//Agrego a la lista de los nodos a ser procesados:
//						if ( !allreadyAddToList[node2->id] ) {
//							ls_Insertar( &nodesToProcessAux, LS_PRIMERO, node2 );
//							allreadyAddToList[ node2->id ] = true;
//						}
//					}
//				} while ( ls_MoverCorriente( &nodes, LS_SIGUIENTE ) );
//				ls_VaciarLight( &nodes );
//			}
//			allreadyProcess[ node->id ] = true;
//			ls_VaciarLight( &(node->ids) );
//		} while ( ls_MoverCorriente( &(nodesToProcess), LS_SIGUIENTE ) );
//		refreshNodesToProcess( &nodesToProcess, &nodesToProcessAux );
//		if ( allReadyDone( fd, personId ) ) {
//			break;
//		}
//		//***************** TODO BORRAR
//		i++;
//		k=0;
//		for (j=0; j<=maxPersonId;j++){
//			if ( allreadyProcess[ j ] == true )
//				k++;
//		}
//		printf("Nodos ya analizados=%i, Celdas ya escritas=%i. Siguiente nivel (distancias=%i):\n", k, m, i);
//		//*****************
//	}
//	return m + completeRow( fd, allreadyProcess, maxPersonId );
//}

/**
 *
 */
void getNodes( TNode *node, TListaSimple *nodes, int maxPersonId ) {
	bool allreadyInsert[maxPersonId+1];
	int i;
	for ( i = 0; i <= maxPersonId; i++ )
		allreadyInsert[i] = false;
	//limpio nodes:
	ls_VaciarLight( nodes );
	TNode *nodeFilm, *nodePerson;
	if ( ls_MoverCorriente( &(node->nodes), LS_PRIMERO ) ) {
		do {
			ls_ElemCorriente( node->nodes, (void**)&nodeFilm );
			if ( ls_MoverCorriente( &(nodeFilm->nodes), LS_PRIMERO ) ) {
				do {
					ls_ElemCorriente( nodeFilm->nodes, (void**)&nodePerson );
					if ( nodePerson->id != node->id && !allreadyInsert[nodePerson->id] ) {
						nodePerson->idFilm = nodeFilm->id;
						ls_Insertar( nodes, LS_PRIMERO, nodePerson );
						allreadyInsert[nodePerson->id] = true;
					}
				} while ( ls_MoverCorriente( &(nodeFilm->nodes), LS_SIGUIENTE ) );
			}
		} while ( ls_MoverCorriente( &(node->nodes), LS_SIGUIENTE ) );
	}
}

/**
 * Limpia la lista 1 y le pasa lo de la 2.
 */
void refreshNodesToProcess( TListaSimple *nodesToProcess, TListaSimple *nodesToProcessAux ) {
	//limpio:
	ls_VaciarLight( nodesToProcess);
	//agrego:
	TNode *node;
	if ( ls_MoverCorriente( nodesToProcessAux, LS_PRIMERO ) ) {
		do {
			ls_ElemCorriente( *nodesToProcessAux, (void**)&node );
			ls_Insertar( nodesToProcess, LS_PRIMERO, node );
		} while ( ls_MoverCorriente( nodesToProcessAux, LS_SIGUIENTE ) );
	}
	//limpio:
	ls_VaciarLight( nodesToProcessAux);
}

/**
 * Agrega los Ids de node a node2
 */
void giveIds( TNode *node2, TNode *node, int maxPersonId ) {
	int *id;
	bool allreadyInsert[maxPersonId+1];
	int i;
	for ( i = 0; i <= maxPersonId; i++ )
		allreadyInsert[i] = false;
	if ( ls_MoverCorriente( &(node2->ids), LS_PRIMERO ) ) {
		do {
			ls_ElemCorriente( node2->ids, (void**)&id );
			allreadyInsert[*id] = true;
		} while ( ls_MoverCorriente( &(node2->ids), LS_SIGUIENTE ) );
	}
	if ( ls_MoverCorriente( &(node->ids), LS_PRIMERO ) ) {
		do {
			ls_ElemCorriente( node->ids, (void**)&id );
			if ( !allreadyInsert[*id] ) {
				ls_Insertar( &(node2->ids), LS_PRIMERO, id );
				allreadyInsert[*id] = true;
			}
		} while ( ls_MoverCorriente( &(node->ids), LS_SIGUIENTE ) );
	}
}

/**
 * Devuelve la cantidad de celdas completadas nuevas.
 */
int process( mHandler *fd, TNode *node2, TNode *node ) {
	int *id;
	regMat reg;
	int i = 0;
	if ( ls_MoverCorriente( &(node2->ids), LS_PRIMERO ) ) {
		do {
			ls_ElemCorriente( node2->ids, (void**)&id );
			if ( !M_EXISTE_REG( fd, node2->id, *id ) ) {
				M_READ( fd, *id, node->id, &reg );
				reg.idPer = node->id;
				reg.idPel = node2->idFilm;
				reg.dist = reg.dist+1;
				M_WRITE( fd, node2->id, *id, &reg );
				i++;
			}
		} while ( ls_MoverCorriente( &(node2->ids), LS_SIGUIENTE ) );
	}
	//actualizo relacion node - node2
	if ( !M_EXISTE_REG( fd, node2->id, node->id ) ) {
		reg.idPer = -1;
		reg.idPel = node2->idFilm;
		reg.dist = 1;
		M_WRITE( fd, node2->id, node->id, &reg );
		i++;
	}
	//agrego el id del nodo desde el q se llego.
	lsAux_insertOrderInt( &(node2->ids), &(node->id) );
	return i;
}

/**
 * Retorna true si toda la fila del personId está completa.
 */
bool allReadyDone( mHandler *fd, int personId ) {
	int i;
	for ( i = 0; i < personId; i++ ) {
		if ( !M_EXISTE_REG(fd, personId, i ) )
			return false;
	}
	return true;
}

/**
 * Completa con  en dist los nodos a los q no se llegó
 */
int completeRow( mHandler *fd, bool *allreadyProcess, int maxPersonId ) {
	int k=0;//TODO BORRAR
	regMat reg;
	reg.idPer = NO_EXISTE_CAMINO_MINIMO;
	reg.idPel = 0;
	reg.dist = NO_EXISTE_CAMINO_MINIMO;
	int i,j;
	for ( i = 0; i <= maxPersonId; i++ ) {
		if ( allreadyProcess[i] ) {
			for ( j = 0; j <= maxPersonId; j++ ) {
				if ( !allreadyProcess[j] )
					if ( M_WRITE( fd, i, j, &reg ) == RES_OK )
						k++;//TODO BORRAR
			}
		}
	}
	printf( "Celdas completadas con NO_EXISTE_CAMINO_MINIMO:%i\n", k );//TODO BORRAR
	return k;
}

/**
 * Crea los archivos de peliculas y personas y los devuelve abierto en los handlers
 */
bool createFiles(fHandler** fp, fHandler** ff, int maxPersonId, int maxFilmId, char* filename_prefix) {
	char filmsFilename[30];
	char personsFilename[30];
	int rc = RES_OK;

	strcpy(filmsFilename, filename_prefix);
	strcpy(personsFilename, filename_prefix);

	strcat(filmsFilename, FILMS_SUFFIX);
	strcat(personsFilename, PERSON_SUFFIX);

	rc = R_CREATE(filmsFilename, sizeof(regPel), maxFilmId);
	if (rc == RES_OK) {
		rc = R_CREATE(personsFilename, sizeof(regPer), maxPersonId);
	}
	if (rc != RES_OK) {
		return false;
	}

	*ff = R_OPEN(filmsFilename, WRITE);
	*fp = R_OPEN(personsFilename, WRITE);

	if (*ff && *fp) {
		return true;
	} else {
		if (*ff) {
			R_CLOSE(*ff);
		}
		if (*fp) {
			R_CLOSE(*fp);
		}
		return false;
	}
}
